﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Components.Forms;

using System.ComponentModel.DataAnnotations;

namespace BlamanticUI.Components.Forms;

/// <summary>
/// A container for one input control without <see cref="InputBox"/> component to display layout in <see cref="Form"/> component.
/// </summary>
public class Field : BlazorChildContentComponentBase, IHasSpan, IHasState, IHasOnDisabled, IHasInline, IDisposable
{
    /// <summary>
    /// Gets or sets the span of column.
    /// </summary>
    [Parameter] [CssClass(" wide", Order = 1, Suffix = true)] public SpanSize? Span { get; set; }

    /// <summary>
    /// Gets or sets the state.
    /// </summary>
    [Parameter] public State? State { get; set; }
    /// <summary>
    /// Gets or sets a value indicating whether this is disabled.
    /// </summary>
    /// <value>
    ///   <c>true</c> if disabled; otherwise, <c>false</c>.
    /// </value>
    [Parameter] public bool Disabled { get; set; }
    /// <summary>
    /// Gets or sets a value indicating whether is inlined layout in row.
    /// </summary>
    [Parameter] public bool Inline { get; set; }
    /// <summary>
    /// Gets or sets this field is required to append a red '*' symbol in front of text.
    /// </summary>
    [Parameter] [CssClass("required")] public bool Required { get; set; }

    /// <summary>
    /// Gets or sets identify the <c>System.ComponentModel.DataAnnotations</c> of field.
    /// </summary>
    [Parameter] public Expression<Func<dynamic>> For { get; set; }
    /// <summary>
    /// Sets a bool value indicate that generate a label value from <see cref="DisplayAttribute"/> when <see cref="For"/> has value.
    /// </summary>
    [Parameter]public bool AutoLabel { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether recover the state of field after valid validation.
    /// </summary>
    [Parameter] public bool RecoverOnValid { get; set; }
    /// <summary>
    /// Gets or sets a callback method to invoke after <see cref="Disabled" /> changed.
    /// </summary>
    [Parameter] public EventCallback<bool> OnDisabled { get; set; }

    /// <summary>
    /// Gets or sets the cascaded edit context.
    /// </summary>
    [CascadingParameter] EditContext CascadedEditContext { get; set; }

    State? _fieldState = default;

    EditContext _boundCascadingEditContext;

    /// <summary>
    /// Method invoked when the component has received parameters from its parent in
    /// the render tree, and the incoming values have been assigned to properties.
    /// </summary>
    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        if (_boundCascadingEditContext != CascadedEditContext)
        {
            if (_boundCascadingEditContext != null)
            {
                _boundCascadingEditContext.OnValidationStateChanged -= CascadingEditContext_ValidationStateChanged;
            }
            _boundCascadingEditContext = CascadedEditContext;
            if (_boundCascadingEditContext != null)
            {
                _boundCascadingEditContext.OnValidationStateChanged += CascadingEditContext_ValidationStateChanged;
            }
        }
    }

    /// <summary>
    /// Cleanup of component
    /// </summary>
    public void Dispose()
    {
        if (_boundCascadingEditContext != null)
        {
            _boundCascadingEditContext.OnValidationStateChanged -= CascadingEditContext_ValidationStateChanged;
        }
    }

    private void CascadingEditContext_ValidationStateChanged(object sender, EventArgs e)
        => StateHasChanged();

    protected override void AddContent(RenderTreeBuilder builder, int sequence)
    {
        if (For != null)
        {
            var member = GetMember(For.Body);
            var requiredAttribute = member?.GetCustomAttribute<RequiredAttribute>();
            Required = requiredAttribute != null;

            var fieldIdentified = FieldIdentifier.Create(For);
            _fieldState = CascadedEditContext.GetState(fieldIdentified, RecoverOnValid);

            builder.AddContent(10, (child =>
             {
                 if (member != null)
                 {
                     var name= member.GetCustomAttribute<DisplayAttribute>()?.Name;
                     child.CreateElement(0, "label", content => content.AddContent(0, name), new { @for = fieldIdentified.FieldName }, !string.IsNullOrEmpty(name));
                     

                     child.AddContent(20, ChildContent);

                     if (CascadedEditContext != null)
                     {
                         var errorMessages = CascadedEditContext.GetValidationMessages(fieldIdentified);
                         if (errorMessages.Any())
                         {

                             child.CreateComponent<Label>(30, label =>
                             {
                                 label.CreateComponent<List>(0, list =>
                                 {
                                     foreach (var error in errorMessages)
                                     {
                                         list.CreateComponent<Item>(0,error);
                                     }
                                 }, new { Bulleted = errorMessages.Count() > 1 });
                             }, new { Pointer = RelativePosition.Above, Basic = true, Color = Color.Red, AdditionalStyle = "position:absolute;display:block;z-index:100" });                            
                         }
                     }
                 }


             }));
        }
        else
        {
            AddChildContent(builder, sequence);
        }
    }

    protected override void BuildCssClass(ICssClassBuilder builder)
    {
        if (_fieldState.HasValue)
        {
            builder.Append(_fieldState.Value.GetCssClass());
        }
        builder.Append("field");
    }

    protected override void BuildStyle(IStyleBuilder builder)
    {
        builder.Append("position:relative", For is not null);
    }

    /// <summary>
    /// Gets the member.
    /// </summary>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    private MemberInfo GetMember(Expression expression)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.MemberAccess:
                return ((MemberExpression)expression).Member;
            case ExpressionType.Convert:
                return GetMember(((UnaryExpression)expression).Operand);
            default:
                break;
        }
        return default;
    }
}
